package com.cmc6.user.service.impl;

import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.RandomUtil;
import com.cmc6.common.constant.BaseConstant;
import com.cmc6.common.util.ApiResult;
import com.cmc6.common.util.MyMailUtil;
import com.cmc6.common.util.ParamUtil;
import com.cmc6.user.exception.BizCodeEnum;
import com.cmc6.user.model.dto.EmailNotBlankDTO;
import com.cmc6.user.model.dto.RegByEmailDTO;
import com.cmc6.user.model.dto.UserLoginInsertOrUpdateDTO;
import com.cmc6.user.service.RegByEmailService;
import com.cmc6.user.service.UserRegHelperService;
import com.cmc6.user.service.UserService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class RegByEmailServiceImpl implements RegByEmailService {

    @Resource
    RedisTemplate<String, String> redisTemplate;
    @Resource
    RedissonClient redissonClient;
    @Resource
    UserService userService;
    @Resource
    UserRegHelperService userRegHelperService;

    /**
     * 注册
     */
    @Override
    @Transactional
    public String regByEmail(RegByEmailDTO dto) {

        // 前端传过来的密码，会先进行 SHA256加密
        // 2021-12-26 追加：前端会在 SHA256加密 之后，再进行一次非对称加密，并且会携带一分钟之后的时间戳
        // 后端会把这个非对称加密，存入 redis（一分钟自动过期），目的：一个非对称加密，只能用一次
        // 先检查 redis中是否存在，存在则说明已经用过了，不存在，则解密之后，校验是否过期，过期了也不能使用

        // 非对称加密，解密 ↓
        String paramValue = ParamUtil.getValueById(BaseConstant.RSA_PRIVATE_KEY_ID); // 获取非对称加密，私钥
        dto.setOrigPassword(userService.rsaDecrypt(dto.getOrigPassword(), paramValue)); // 非对称加密：解密
        dto.setPassword(userService.rsaDecrypt(dto.getPassword(), paramValue)); // 非对称加密：解密
        // 非对称加密，解密 ↑

        userService.checkOrigPassword(dto.getOrigPassword()); // 检查密码是否合法

        String key = BaseConstant.PRE_LOCK_EMAIL_REG_CODE + dto.getEmail();
        // 增加分布式锁
        // 获取一把锁，只要锁名字一样，就是同一把锁，即：同一时间只能有一个邮箱进行 注册/修改
        RLock lock = redissonClient.getLock(BaseConstant.PRE_REDISSON + key);
        lock.lock();

        try {
            // 校验 code
            userRegHelperService.checkCodeByRedis(key, dto.getCode());
            regByEmailNext(dto);
            return "注册成功";
        } finally {
            // 解除分布式锁
            lock.unlock();
        }
    }

    /**
     * 往数据库插入 注册的数据
     */
    private void regByEmailNext(RegByEmailDTO dto) {

        UserLoginInsertOrUpdateDTO userLoginInsertOrUpdateDTO = new UserLoginInsertOrUpdateDTO();
        userLoginInsertOrUpdateDTO.setEmail(dto.getEmail());
        userLoginInsertOrUpdateDTO.setPassword(dto.getPassword());
        userLoginInsertOrUpdateDTO.setPhone("");

        userRegHelperService.regBase(userLoginInsertOrUpdateDTO, true);

    }

    /**
     * 发送验证码
     */
    @Override
    public String sendCode(EmailNotBlankDTO dto, int type) {

        // 增加分布式锁
        // 获取一把锁，只要锁名字一样，就是同一把锁，即：同一时间只能有一个邮箱进行 注册/修改
        RLock lock =
            redissonClient.getLock(BaseConstant.PRE_REDISSON + BaseConstant.PRE_LOCK_EMAIL_REG_CODE + dto.getEmail());
        lock.lock();

        try {
            return sendCodeForLock(dto, type);
        } finally {
            // 解除分布式锁
            lock.unlock();
        }

    }

    /**
     * 发送验证码：加锁之后的操作
     */
    private String sendCodeForLock(EmailNotBlankDTO dto, int type) {

        // 判断邮箱是否存在
        if (userService.checkEmailExist(dto.getEmail(), null)) {
            ApiResult.error(BizCodeEnum.EMAIL_HAS_BEEN_REGISTERED);
        }

        StrBuilder strBuilder;
        String subject;
        if (type == 1) {
            strBuilder = new StrBuilder("尊敬的用户您好，您本次注册的验证码是（10分钟内有效）：");
            subject = "邮箱注册";
        } else if (type == 2) {
            strBuilder = new StrBuilder("尊敬的用户您好，您本次邮箱修改的验证码是（10分钟内有效）：");
            subject = "邮箱修改";
        } else {
            strBuilder = new StrBuilder("尊敬的用户您好，您本次邮箱绑定的验证码是（10分钟内有效）：");
            subject = "邮箱绑定";
        }

        // 生成随机码，注意：这里是写死的，只生成6位数，如果需要改，则controller层code的正则表达式校验也需要改
        String code = RandomUtil.randomNumbers(6).toUpperCase();
        strBuilder.append(code);

        MyMailUtil.send(dto.getEmail(), subject, strBuilder.toString(), false);

        // 保存到redis中，格式：{PRE_LOCK_EMAIL_REG_CODE + email : code}
        // 设置10分钟过期
        redisTemplate.opsForValue()
            .set(BaseConstant.PRE_LOCK_EMAIL_REG_CODE + dto.getEmail(), code, BaseConstant.MINUTE_10_EXPIRE_TIME,
                TimeUnit.MILLISECONDS);

        return "发送成功";

    }

}
